| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- 'use client';
- import { useEffect, useState } from 'react';
- import Link from 'next/link';
- import { useParams } from 'next/navigation';
- import { CheckCircle2 } from 'lucide-react';
- import { fetchApi, getDateTime } from '@/lib/utils/client';
- import Loading from '@/app/component/Loading';
- import useCart from '@/hooks/useCart';
- import type { OrderDetail } from '@/types/store';
- function formatPrice(n: number): string
- {
- return n.toLocaleString('ko-KR');
- }
- export default function OrderCompletePage()
- {
- const params = useParams<{ orderID: string }>();
- const orderID = Number(params.orderID);
- const { clear } = useCart();
- const [order, setOrder] = useState<OrderDetail|null>(null);
- const [loading, setLoading] = useState(true);
- useEffect(() => {
- if (Number.isNaN(orderID) || orderID <= 0) {
- setLoading(false);
- return;
- }
- (async () => {
- const res = await fetchApi<OrderDetail>(`/api/store/orders/${orderID}`, { silent: true });
- if (res.success && res.data) {
- setOrder(res.data);
- // 결제 성공 페이지 진입 시점에 cart 비우기 (사용자가 결제 완료를 확인한 시점)
- clear();
- }
- setLoading(false);
- })();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [orderID]);
- if (loading) {
- return <Loading />;
- }
- if (!order) {
- return (
- <div className='container mx-auto max-w-md px-4 py-20 text-center'>
- <p className='text-neutral-500 mb-4'>주문 정보를 찾을 수 없습니다.</p>
- <Link href='/orders' className='text-blue-600 dark:text-blue-400 hover:underline'>
- 주문 목록으로
- </Link>
- </div>
- );
- }
- const hasDigital = order.items.some(i => i.type === 2);
- const hasPhysical = order.items.some(i => i.type === 1);
- const guideText = hasDigital && !hasPhysical
- ? '쿠폰은 보관함에서 확인하실 수 있어요.'
- : hasPhysical && !hasDigital
- ? '배송 상태는 주문 목록에서 확인하실 수 있어요.'
- : '쿠폰은 보관함, 배송 상태는 주문 목록에서 확인하실 수 있어요.';
- return (
- <div className='container mx-auto max-w-2xl px-4 py-10'>
- {/* 헤더 */}
- <div className='text-center mb-8'>
- <CheckCircle2 className='size-16 text-green-500 mx-auto mb-3' />
- <h1 className='text-2xl font-bold mb-1'>주문이 완료되었습니다</h1>
- <p className='text-neutral-500 text-sm'>{guideText}</p>
- </div>
- {/* 주문 요약 카드 */}
- <div className='border border-neutral-200 dark:border-neutral-800 rounded-lg bg-white dark:bg-neutral-900 overflow-hidden mb-6'>
- <div className='px-5 py-3 border-b border-neutral-200 dark:border-neutral-800 flex justify-between items-center gap-2 flex-wrap'>
- <div>
- <div className='text-xs text-neutral-500'>주문번호</div>
- <div className='font-mono font-semibold text-sm'>{order.orderNumber}</div>
- </div>
- <div className='text-right'>
- <div className='text-xs text-neutral-500'>결제 일시</div>
- <div className='text-sm'>{getDateTime(order.paidAt ?? order.createdAt)}</div>
- </div>
- </div>
- <table className='w-full text-sm'>
- <tbody>
- {order.items.map((item) => (
- <tr key={item.id} className='border-b border-neutral-100 dark:border-neutral-800/60 last:border-0'>
- <td className='px-5 py-3'>
- <div className='font-medium'>{item.productName}</div>
- <div className='text-xs text-neutral-500 mt-0.5'>
- {item.type === 2 ? '쿠폰' : '실물'} · 수량 {item.quantity}
- </div>
- </td>
- <td className='text-right px-5 py-3 font-mono whitespace-nowrap'>
- {formatPrice(item.unitPrice * item.quantity)}P
- </td>
- </tr>
- ))}
- </tbody>
- </table>
- <div className='px-5 py-3 border-t border-neutral-200 dark:border-neutral-800 flex justify-between items-center bg-neutral-50 dark:bg-neutral-800/40'>
- <span className='text-sm font-semibold'>결제 금액</span>
- <span className='text-lg font-bold text-red-600 dark:text-red-400 font-mono'>
- {formatPrice(order.totalAmount)}P
- </span>
- </div>
- {order.channelName && (
- <div className='px-5 py-2 text-xs text-neutral-500 border-t border-neutral-100 dark:border-neutral-800/60'>
- 후원 채널: <span className='font-semibold'>{order.channelName}</span>
- </div>
- )}
- </div>
- {/* CTA 버튼 */}
- <div className='flex flex-col sm:flex-row gap-2'>
- {hasDigital && (
- <Link
- href='/inventory'
- className={`flex-1 px-4 py-3 rounded text-center text-sm font-semibold transition-colors ${
- !hasPhysical
- ? 'bg-blue-600 text-white hover:bg-blue-700'
- : 'border border-blue-600 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-950'
- }`}
- >
- 보관함으로
- </Link>
- )}
- <Link
- href={`/orders/${order.id}`}
- className={`flex-1 px-4 py-3 rounded text-center text-sm font-semibold transition-colors ${
- hasPhysical
- ? 'bg-blue-600 text-white hover:bg-blue-700'
- : 'border border-neutral-300 dark:border-neutral-700 hover:bg-neutral-50 dark:hover:bg-neutral-800'
- }`}
- >
- 주문 상세로
- </Link>
- </div>
- <Link
- href='/store'
- className='text-xs text-blue-600 dark:text-blue-400 hover:underline mt-4 block text-center'
- >
- 쇼핑 계속하기 〉
- </Link>
- </div>
- );
- }
|